package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/olivere/elastic/v7"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"sale_system/goods_srv/global"
	"sale_system/goods_srv/model"
	"sale_system/goods_srv/proto"
)

type GoodsServer struct {
	proto.UnimplementedGoodsServer // 一定需要实现这个接口
}

func (s *GoodsServer) mustEmbedUnimplementedGoodsServer() {
	//TODO implement me
	panic("implement me")
}

// Paginate 分页函数
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {

		if page <= 0 {
			page = 1
		}
		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}
		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

func ModelToResponse(goods model.Goods) proto.GoodsInfoResponse {
	return proto.GoodsInfoResponse{
		Id:              goods.ID,
		CategoryId:      goods.CategoryID,
		Name:            goods.Name,
		GoodsSn:         goods.GoodsSn,
		ClickNum:        goods.ClickNum,
		SoldNum:         goods.SoldNum,
		FavNum:          goods.FavNum,
		MarketPrice:     goods.MarketPrice,
		ShopPrice:       goods.ShopPrice,
		GoodsBrief:      goods.GoodsBrief,
		ShipFree:        goods.ShipFree,
		GoodsFrontImage: goods.GoodsFrontImage,
		IsNew:           goods.IsNew,
		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		DescImages:      goods.DescImages,
		Images:          goods.Images,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},
		Brand: &proto.BrandInfoResponse{
			Id:   goods.Brands.ID,
			Name: goods.Brands.Name,
			Logo: goods.Brands.Logo,
		},
	}
}

func (s *GoodsServer) GoodsList(ctx context.Context, request *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
	// 搜索商品列表：可能通过关键词、通过新品、热门商品、价格区间等进行查询
	// 外部场景：用户可能同时使用这些查询字段，所以需要考虑数据的链接

	// 通过ES进行查询
	query := elastic.NewBoolQuery()

	//// 维护一个局部的localDB，查询进行过滤
	//localDB := global.DB.Model(model.Goods{})

	// 模糊匹配我们通过match进行查询
	if request.KeyWords != "" {
		query = query.Must(elastic.NewMultiMatchQuery(request.KeyWords, "name", "goods_brief"))
	}
	// 精确字段我们通过term进行查询
	if request.IsHot == true {
		query = query.Filter(elastic.NewTermQuery("is_hot", request.IsHot))
	}
	if request.IsNew {

		query = query.Filter(elastic.NewTermQuery("is_new", request.IsNew))
	}

	if request.PriceMin > 0 {
		query = query.Filter(elastic.NewRangeQuery("shop_price").Gte(request.PriceMin))
	}
	if request.PriceMax > 0 {
		query = query.Filter(elastic.NewRangeQuery("shop_price").Lte(request.PriceMax))
	}
	// 传进来的是品牌ID
	if request.Brand > 0 {
		query = query.Filter(elastic.NewTermQuery("brands_id", request.Brand))
	}

	var subQuery string
	categoryIDs := make([]interface{}, 0)

	// 通过category来查询商品，一级->二级 二级->三级 三级->商品
	if request.TopCategory > 0 {
		var category model.Category
		result := global.DB.First(&category, request.TopCategory)
		if result.RowsAffected == 0 {
			return nil, status.Error(codes.NotFound, "商品分类不存在")
		}

		// 根据分类等级实现子查询
		if category.Level == 1 {
			// 首先select id from category WHERE parent_category_id=request.TopCategory   查找到一级类目下的id列表
			// 然后select id from category where parent_category_id in 一级类目的id列表 获取到所有的三级类目的id
			subQuery = fmt.Sprintf("select id from category where parent_category_id in (select id from category WHERE parent_category_id=%d)", request.TopCategory)
		} else if category.Level == 2 {
			subQuery = fmt.Sprintf("select id from category WHERE parent_category_id=%d", request.TopCategory)
		} else if category.Level == 3 {
			subQuery = fmt.Sprintf("select id from category WHERE id=%d", request.TopCategory)
		}

		// 从数据库中读取到，对应品类的id，并写入到categoryIDs中
		type Result struct {
			ID int32
		}
		var results []Result
		// 获取查询到数据的切片
		global.DB.Model(model.Category{}).Raw(subQuery).Scan(&results)

		for _, result := range results {
			categoryIDs = append(categoryIDs, result.ID)
		}
		// 生成terms查询
		query = query.Filter(elastic.NewTermsQuery("category_id", categoryIDs...))
	}

	// 获取分页参数
	page := int(request.Pages)
	pageSize := int(request.PagePerNums)
	if page <= 0 {
		page = 1
	}
	switch {
	case pageSize > 100:
		pageSize = 100
	case pageSize <= 0:
		pageSize = 10
	}

	// 通过ES进行数据的查询
	result, err := global.ES.Search().Index(model.EsGoods{}.GetIndexName()).Query(query).
		From(page).Size(pageSize).Do(context.Background())

	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	rsp := proto.GoodsListResponse{}
	rsp.Total = int32(result.Hits.TotalHits.Value)

	goodsIDs := make([]int32, 0)
	for _, value := range result.Hits.Hits {

		goodsInfo := model.EsGoods{}
		_ = json.Unmarshal(value.Source, &goodsInfo)
		goodsIDs = append(goodsIDs, goodsInfo.ID)
	}

	// 查询id在某个数组之间得知
	var goods []model.Goods
	ret := global.DB.Model(model.Goods{}).Preload("Category").Preload("Brands").Find(&goods, goodsIDs)
	if ret.Error != nil {
		return nil, status.Error(codes.Internal, ret.Error.Error())
	}

	for _, good := range goods {

		goodInfo := ModelToResponse(good)      // 将数据库读取到的商品信息写入到goodInfo中
		rsp.Data = append(rsp.Data, &goodInfo) // 添加到响应字段中的切片模块
	}

	return &rsp, nil
}

// 现在用户提交订单有多个商品，你得批量查询商品的信息吧
func (s *GoodsServer) BatchGetGoods(ctx context.Context, request *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {

	var goods []model.Goods
	// 注意这里的request.Id类型是一个切片！！！存放多个id
	result := global.DB.Where(request.Id).Find(&goods) // 注意where是生成查询语句，而Find、First才是执行SQL进行查询
	if result.Error != nil {
		return nil, result.Error
	}

	rsp := proto.GoodsListResponse{}
	rsp.Total = int32(result.RowsAffected)

	for _, good := range goods {

		goodInfo := ModelToResponse(good)
		rsp.Data = append(rsp.Data, &goodInfo)
	}
	return &rsp, nil
}

func (s *GoodsServer) GetGoodsDetail(ctx context.Context, request *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {

	var goods model.Goods

	result := global.DB.First(&goods, request.Id)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "商品不存在")
	}
	goodsInfo := ModelToResponse(goods)
	return &goodsInfo, nil
}

func (s *GoodsServer) CreateGoods(ctx context.Context, request *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {

	// 需要维护对象，从数据库中取到值
	var category model.Category
	result := global.DB.First(&category, request.CategoryId)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "品类不存在")
	}
	var brand model.Brands
	result = global.DB.First(&brand, request.BrandId)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "品牌不存在")
	}

	// 图片如何进行上传和存储？？ 普通的文件上传不再适用
	goods := model.Goods{
		Brands:          brand,
		BrandsID:        brand.ID,
		Category:        category,
		CategoryID:      category.ID,
		Name:            request.Name,
		GoodsSn:         request.GoodsSn,
		MarketPrice:     request.MarketPrice,
		ShopPrice:       request.ShopPrice,
		GoodsBrief:      request.GoodsBrief,
		ShipFree:        request.ShipFree,
		Images:          request.Images,
		DescImages:      request.DescImages,
		GoodsFrontImage: request.GoodsFrontImage,
		IsNew:           request.IsNew,
		IsHot:           request.IsHot,
		OnSale:          request.OnSale,
	}
	// 注意我们在model/goods.go中实现了钩子方法，用来实现mysql数据存储后，进行ES存储，如果ES存储失败了，那么这时Save会受到Err
	// 所以我们需要设置事务，并进行回滚
	tx := global.DB.Begin()
	result = tx.Save(&goods)
	if result.Error != nil {
		tx.Rollback()
		return nil, status.Error(codes.Internal, result.Error.Error())
	}
	tx.Commit()

	return &proto.GoodsInfoResponse{Id: goods.ID}, nil
}

func (s *GoodsServer) DeleteGoods(ctx context.Context, request *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {

	result := global.DB.First(&model.Goods{}, request.Id)
	if result.RowsAffected == 0 {
		return &emptypb.Empty{}, status.Error(codes.NotFound, "商品不存在")
	}

	// 注意我们在model/goods.go中实现了钩子方法，用来实现mysql数据存储后，进行ES存储，如果ES存储失败了，那么这时Save会受到Err
	// 所以我们需要设置事务，并进行回滚
	tx := global.DB.Begin()
	result = tx.Delete(&model.Goods{BaseModel: model.BaseModel{ID: request.Id}}, request.Id)
	if result.Error != nil {
		tx.Rollback()
		return &emptypb.Empty{}, status.Error(codes.Internal, result.Error.Error())
	}
	tx.Commit()

	return &emptypb.Empty{}, nil
}

func (s *GoodsServer) UpdateGoods(ctx context.Context, request *proto.CreateGoodsInfo) (*emptypb.Empty, error) {

	var goods model.Goods
	if result := global.DB.First(&goods, request.Id); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	var category model.Category
	if result := global.DB.First(&category, request.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}
	var brand model.Brands
	if result := global.DB.First(&brand, request.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "品牌不存在")
	}

	goods.Brands = brand
	goods.BrandsID = brand.ID
	goods.Category = category
	goods.CategoryID = category.ID
	goods.Name = request.Name
	goods.GoodsSn = request.GoodsSn
	goods.MarketPrice = request.MarketPrice
	goods.ShopPrice = request.ShopPrice
	goods.GoodsBrief = request.GoodsBrief
	goods.ShipFree = request.ShipFree
	goods.Images = request.Images
	goods.DescImages = request.DescImages
	goods.GoodsFrontImage = request.GoodsFrontImage
	goods.IsNew = request.IsNew
	goods.IsHot = request.IsHot
	goods.OnSale = request.OnSale

	// 注意我们在model/goods.go中实现了钩子方法，用来实现mysql数据存储后，进行ES存储，如果ES存储失败了，那么这时Save会受到Err
	// 所以我们需要设置事务，并进行回滚
	tx := global.DB.Begin()
	result := tx.Save(&goods)
	if result.Error != nil {
		tx.Rollback()
		return nil, status.Error(codes.Internal, result.Error.Error())
	}
	tx.Commit()
	return &emptypb.Empty{}, nil
}
